#include "prot-filer/read_utils.h"

namespace prot_filer
{

CustomReadBuffer::CustomReadBuffer(unsigned int number_of_angles_id, istream& file) :
    angles_id_size(helper.angle_id_size_in_bits(number_of_angles_id)),
    read_buffer(0),
    available_reads(helper.sizeof_in_bits(read_buffer) / angles_id_size),
    file(file),
    mask((1 << angles_id_size) - 1),
    reads_per_buffer(available_reads)
{
    helper.read_var(file, read_buffer);
}

void CustomReadBuffer::physical_read()
{
    helper.read_var(file, read_buffer);
    available_reads = reads_per_buffer;
}

void CustomReadBuffer::read_var(AngleId& var)
{
    if (available_reads == 0)
    {
        physical_read();
    }
    --available_reads;
    const AngleId result = read_buffer &  mask;
    read_buffer >>= angles_id_size;
    var = result;
}

void CustomReadBuffer::read(AngleIdPair& angle_id_pair)
{
    read_var(angle_id_pair.fi);
    read_var(angle_id_pair.si);
}

CustomWriteBuffer::CustomWriteBuffer(unsigned int number_of_angles_id, ostream& file) :
    angles_id_size(helper.angle_id_size_in_bits(number_of_angles_id)),
    write_buffer(0),
    available_writes(helper.sizeof_in_bits(write_buffer) / angles_id_size),
    file(file),
    original_mask((1 << angles_id_size) - 1),
    shifts(0),
    writes_per_buffer(available_writes)
{}

void CustomWriteBuffer::flush_buffer()
{
    helper.write_var(file, write_buffer);
    write_buffer = 0;
    available_writes = writes_per_buffer;
}

void CustomWriteBuffer::write_var(AngleId var)
{
    if (available_writes == 0)
    {
        flush_buffer();
    }
    --available_writes;
    var &= original_mask;
    var <<= shifts;
    write_buffer |= var;
    shifts += angles_id_size;
}

void CustomWriteBuffer::write(const AngleIdPair& angle_id_pair)
{
    write_var(angle_id_pair.fi);
    write_var(angle_id_pair.si);
}

unsigned int CompressedHelper::get_mapping_file_size_in_bytes(const AnglesMapping* angles_mapping)
{
    const unsigned int ndat = angles_mapping->get_mapping_size();
    const unsigned int ndat_nres_size = 2 * sizeof(ndat); // sizeof(ndat) = sizeof(nres).
    const AngleValue some_angle_value(0.0f); // Initialized because otherwise would cause a compile time error.
    const unsigned int result = ndat_nres_size + ndat * 2 * sizeof(some_angle_value); // Each angle value in fis and sis.
    return result;
}

unsigned int CompressedHelper::get_structure_file_size_in_bytes(const AnglesData* angles_data)
{
    const BufferType write_buffer(0); // Initialized because otherwise would cause a compile time error.
    const unsigned int number_of_angles_id = angles_data->get_angles_mapping().get_mapping_size();
    const unsigned int angles_id_size_in_bits = CompressedHelper::angle_id_size_in_bits(number_of_angles_id);
    const unsigned int available_writes(sizeof_in_bits(write_buffer) / angles_id_size_in_bits);
    const unsigned int nres = angles_data->nres - 1;
    unsigned int result = (nres * 2 / available_writes) * sizeof(write_buffer); // Each angle id.
    result += ((nres * 2 % available_writes) != 0) * sizeof(write_buffer); // If there were some angles id left.
    ++result; // END_OF_DATA char;
    return result;
}

unsigned int CompressedHelper::get_last_position(istream& file, const AnglesMapping* mapping, const AnglesData* data)
{
    int current = file.tellg();
    file.seekg(0, std::ios::end);
    int end_pos = file.tellg();
    file.seekg(current);
    int mapping_size = get_mapping_file_size_in_bytes(mapping);
    int structure_size = get_structure_file_size_in_bytes(data);
    return (end_pos - mapping_size) / structure_size;
}

AnglesMapping* CompressedHelper::read_mapping(istream& file)
{
    file.seekg(0);

    unsigned int ndat;
    read_var(file, ndat);
    unsigned int nres;
    read_var(file, nres);

    std::vector<AngleValue> fis;
    std::vector<AngleValue> sis;

    for (unsigned int i = 0 ; i < ndat ; ++i)
    {
        AngleValue temp;
        read_var(file, temp);
        fis.push_back(temp);
    }

    for (unsigned int i = 0 ; i < ndat ; ++i)
    {
        AngleValue temp;
        read_var(file, temp);
        sis.push_back(temp);
    }

    AnglesMapping* angles_mapping = new AnglesMapping(nres);
    for (unsigned int i = 0 ; i < ndat ; ++i)
    {
        angles_mapping->set_mapping(fis[i], sis[0]);
    }
    return angles_mapping;
}

void CompressedHelper::write_mapping(ostream& file, const AnglesMapping& angles_mapping)
{
    const unsigned int ndat = angles_mapping.get_mapping_size();
    write_var(file, ndat);
    write_var(file, angles_mapping.get_nres());

    for (unsigned int i = 0 ; i < ndat ; ++i)
    {
        write_var(file, angles_mapping.get_fi_value(i));
    }

    for (unsigned int i = 0 ; i < ndat ; ++i)
    {
        write_var(file, angles_mapping.get_si_value(i));
    }
}
}
